﻿using System;
using System.Data;
using System.Runtime.Serialization;
using System.Text;
using System.Web;
using K3R.PostcodeAnywhere.PostcodeAnywhereService;
using System.Collections.Generic;
using K3R.Generics;

namespace K3R.PostcodeAnywhere {
	public enum Unit {
		Mile, Kilometre, NuaticalMile
	}

	[DataContract]
	public class GridReference {
		[DataMember(Name = "easting")] public int Easting { get; set; }
		[DataMember(Name = "northing")] public int Northing { get; set; }
		[DataMember(Name = "latitude")] public double Latitude { get; set; }
		[DataMember(Name = "longitude")] public double Longitude { get; set; }

		public static GridReference Load(GeocodeResult geoRes) {
			return new GridReference() {
				Easting = geoRes.GridEastM,
				Northing = geoRes.GridNorthM,
				Latitude = geoRes.Wgs84Latitude,
				Longitude = geoRes.Wgs84Longitude,
			};
		}
	}

	[DataContract]
	public class Address {
		[DataMember(Name = "address1")] public string Address1 { get; set; }
		[DataMember(Name = "address2")] public string Address2 { get; set; }
		[DataMember(Name = "address3")] public string Address3 { get; set; }
		[DataMember(Name = "address4")] public string Address4 { get; set; }
		[DataMember(Name = "address5")] public string Address5 { get; set; }
		[DataMember(Name = "town")] public string Town { get; set; }
		[DataMember(Name = "county")] public string County { get; set; }
		[DataMember(Name = "country")] public string Country { get; set; }
		[DataMember(Name = "postcode")] public string Postcode { get; set; }

		internal static Address Convert(PostcodeAnywhereService.Address ukAddress) {
			return new Address {
				Address1 = ukAddress.Line1,
				Address2 = ukAddress.Line2,
				Address3 = ukAddress.Line3,
				Address4 = ukAddress.Line4,
				Address5 = ukAddress.Line5,
				Town = ukAddress.PostTown,
				County = ukAddress.County,
				Country = "United Kingdom",
				Postcode = ukAddress.Postcode
			};
		}
	}

	public class Lookup {
		#region Protected Helpers
		protected static StringBuilder GetURL(string method, string action, string type) {
			StringBuilder url = new StringBuilder();

			url.AppendFormat("http://services.postcodeanywhere.co.uk/{0}.aspx?", method);
			url.AppendFormat("&action={0}", action);
			url.AppendFormat("&type={0}", type);
			url.AppendFormat("&account_code={0}", HttpUtility.UrlEncode(Settings.AccountCode));
			url.AppendFormat("&license_code={0}", HttpUtility.UrlEncode(Settings.LicenceKey));

			return url;
		}

		protected static GridReference ToGridReference(DataSet ds) {
			GridReference gridRef = null;

			if (ds.Tables.Count == 1 && ds.Tables[0].Columns.Count == 2)
				throw new Exception(ds.Tables[0].Rows[0].ItemArray[1].ToString());
			else if (ds.Tables.Count == 1 && ds.Tables[0].Columns.Count > 2) {
				DataRow row = ds.Tables[0].Rows[0];
				gridRef = new GridReference() {
					Easting = Convert.ToInt32(row[0]),
					Northing = Convert.ToInt32(row[1]),
					Latitude = Convert.ToDouble(row[2]),
					Longitude = Convert.ToDouble(row[3])
				};
			}

			return gridRef;
		}

		protected static double Deg2Rad(double deg) {
			return (deg * Math.PI / 180.0);
		}

		protected static double Rad2Deg(double rad) {
			return (rad / Math.PI * 180.0);
		}
		#endregion

		#region Public Helpers
		public static double GetDistance(double latitude1, double longitude1, double latitude2, double longitude2, Unit unit) {
			double theta = longitude1 - longitude2;
			double distance = Math.Sin(Deg2Rad(latitude1)) * Math.Sin(Deg2Rad(latitude2)) + Math.Cos(Deg2Rad(latitude1)) * Math.Cos(Deg2Rad(latitude2)) * Math.Cos(Deg2Rad(theta));
			distance = Math.Acos(distance);
			distance = Rad2Deg(distance);
			distance = distance * 60 * 1.1515;
			if (unit == Unit.Kilometre) {
				distance = distance * 1.609344;
			}
			else if (unit == Unit.NuaticalMile) {
				distance = distance * 0.8684;
			}
			return distance;
		}
		#endregion

		#region Postcode Anywhere Calls
		public static GridReference GetGridReference(string postcode) {
			GridReference gridRef = null;

			if (postcode.Length < 10) {
				LookupUK lookup = new LookupUK();
				GeocodeResults results = lookup.GeocodePostcode(postcode, "", "", Settings.AccountCode, Settings.LicenceKey, "");

				if (results.Results.Length > 0)
					gridRef = GridReference.Load(results.Results[0]);
			}

			return gridRef;
		}

		public static GridReference ConvertToLatLon(int easting, int northing) {
			StringBuilder url = GetURL("dataset", "convert", "coordinates");
			DataSet ds = new DataSet();

			url.AppendFormat("&easting={0}", HttpUtility.UrlEncode(easting.ToString()));
			url.AppendFormat("&northing={0}", HttpUtility.UrlEncode(northing.ToString()));
;
			ds.ReadXml(url.ToString());

			return ToGridReference(ds);
		}

		public static GridReference ConvertToEastNorth(double latitude, double longitude) {
			StringBuilder url = GetURL("dataset", "convert", "coordinates");
			DataSet ds = new DataSet();

			url.AppendFormat("&latitude={0}", HttpUtility.UrlEncode(latitude.ToString()));
			url.AppendFormat("&longitude={0}", HttpUtility.UrlEncode(longitude.ToString()));

			ds.ReadXml(url.ToString());

			return ToGridReference(ds);
		}

		public static Address FetchAddress(string key) {
			var lookup = new LookupUK();
			AddressResults addressResults = lookup.FetchAddress(key, enLanguage.enLanguageEnglish, enContentType.enContentStandardAddress, Settings.AccountCode, Settings.LicenceKey, "");

			Address address = null;
			if (addressResults.Results != null && addressResults.Results.Length > 0)
				address = Address.Convert(addressResults.Results[0]);

			return address;
		}

		public static Address FastAddress(string postcode, string building) {
			var lookup = new LookupUK();
			AddressResults addressResults = lookup.FastAddress(postcode, building, enLanguage.enLanguageEnglish,
				enContentType.enContentStandardAddress, Settings.AccountCode, Settings.LicenceKey, "");

			Address address = null;
			if (addressResults.Results != null && addressResults.Results.Length > 0)
				address = Address.Convert(addressResults.Results[0]);

			return address;
		}

		public static List<SerializableKeyValuePair<string, string>> ByPostcode(string postcode) {
			var lookup = new LookupUK();
			var interimResults = lookup.ByPostcode(postcode, Settings.AccountCode, Settings.LicenceKey, "");

			var list = new List<SerializableKeyValuePair<string, string>>();
			if (interimResults.Results != null)
				foreach (InterimResult ir in interimResults.Results)
					list.Add(new SerializableKeyValuePair<string, string> {
						Key = ir.Id,
						Value = ir.Description
					});

			return list;
		} 
		#endregion
	}
}